#include "LEDs.h"
#include "board.h"
#include "stm32g0xx.h"
#include "kl_libG070.h"
#include "color.h"

#if 1 // ============================= Variables ===============================
static const Timer_t itmr{LEDS_TIM}; // Latch generator

#define LEDS_TIM_SHIFT      4
#define LEDS_TIM_PULSE_LEN  1

// Counting on both fronts => x2 pulses. 5 ICs, 8 bits each
#define LEDS_TIM_TOP        ((LED_IC_CNT * 8UL) * 2UL - 1UL)

static void LedsDmaIrqHandler(void*p, uint32_t flags);

static const Spi_t ispi{LEDS_SPI};
const DMA_t idma{LEDS_DMA_TX_CHNL, LEDS_DMA_TX_REQID, LedsDmaIrqHandler};

// Ordinal DMA mode: irq
#define LEDS_DMA_MODE (\
        DMA_PRIORITY_VERYHIGH | \
        DMA_MSIZE_8_BIT | \
        DMA_PSIZE_8_BIT | \
        DMA_MEM_INC |       \
        DMA_DIR_MEM2PER |  \
        DMA_TCIE )

PinOutputPWM_t central_LED{CENTRAL_LED};
static volatile bool new_pic_is_ready = false;
#endif

#if 1 // ========================= RawBuf ======================================
#define LED_RAWBUF_SZ      (TIMESLOT_TOP * LED_IC_CNT) // Size in bytes

struct RawBuf_t {
    uint8_t buf1[LED_RAWBUF_SZ], buf2[LED_RAWBUF_SZ], *read_buf;
    void Reset() {
        read_buf = buf1;
        for(uint32_t i=0; i<LED_RAWBUF_SZ; i++) buf1[i] = 0x00;
    }
    volatile uint8_t* GetWriteBuf() { return (volatile uint8_t*)((read_buf == buf1)? buf2 : buf1); }
    void SwitchReadBuf() { read_buf = (read_buf == buf1)? buf2 : buf1; }
} raw_buf;
#endif

void LedsDmaIrqHandler(void*p, uint32_t flags) {
//    DBG_HI();
    idma.Disable();
    if(new_pic_is_ready) {
        raw_buf.SwitchReadBuf();
        new_pic_is_ready = false;
//        DBG_LO();
    }
    // Prepare DMA
    idma.SetMemoryAddr(raw_buf.read_buf);
    idma.SetTransferDataCnt(LED_RAWBUF_SZ);
    // Wait transmission to complete (4 bytes in FIFO after last DMA transaction)
    while(LEDS_SPI->SR & SPI_SR_FTLVL);
    while(LEDS_SPI->SR & SPI_SR_BSY);
    itmr.SetCounter(0); // Reset counter to return it back to sync, in case it is lost somehow
    idma.Enable(); // Start transmission
//    DBG_LO();
}

namespace Leds {

void Init() {
#if 1 // ==== Dimmer, SPI & DMA, Power, Central LED ====
    raw_buf.Reset();
    PinSetupOut(OUT_EN_PIN, omPushPull);
    PinSetHi(OUT_EN_PIN);
    // SPI
    PinSetupAlterFunc(LED_CLK, psHigh);
    PinSetupAlterFunc(LED_DATA, psHigh);
    ispi.Setup(boMSB, cpolIdleLow, cphaFirstEdge, 32000000, bitn8);
    ispi.EnableTxDma();
    // DMA
    idma.Init(&LEDS_SPI->DR, raw_buf.read_buf, LEDS_DMA_MODE, LED_RAWBUF_SZ);
    idma.Enable();
    // Power
    PinSetupOut(LED_PWR_EN, omPushPull);
    // Init central LED
    central_LED.Init();
#endif

#if 1 // ==== Latch generator ====
    itmr.Init();
    // Input1 as clock input
    PinSetupAlterFunc(LATCH_TMR_IN, psVeryHigh);
    // CC1 channel is configured as input, IC1 is mapped on TI1
    itmr.SetupInput1(0b01, Timer_t::pscDiv1, risefallBoth);
    itmr.SelectSlaveMode(smExternal); // External clock mode 1
    itmr.SetTriggerInput(tiTI1FP1);
    // Output4 as latch output
    PinSetupAlterFunc(LATCH_PIN, psLow);
    itmr.SetTopValue(LEDS_TIM_TOP);
    // Combined PWM mode 2: OC4REFC is the logical AND between OC4REF and OC3REF
    itmr.SetupOutput3(0b1100);
    itmr.SetupOutput4(0b1101);
    itmr.EnableCCOutput4();
    itmr.SetCCR4(LEDS_TIM_TOP - LEDS_TIM_SHIFT); // When set Hi
    itmr.SetCCR3(LEDS_TIM_TOP - LEDS_TIM_SHIFT + LEDS_TIM_PULSE_LEN); // When set Lo
    itmr.Enable();
#endif
    PinSetLo(OUT_EN_PIN);
    ispi.Enable();
}

void PowerOn()  { PinSetHi(LED_PWR_EN); }
void PowerOff() { PinSetLo(LED_PWR_EN); }

void ShowPic(uint16_t *pic) {
    new_pic_is_ready = false;
//    DBG_HI();
    // Where to write to and read from
    volatile uint8_t *pdst = raw_buf.GetWriteBuf() + (LED_IC_CNT - 1);
    uint16_t *psrc = pic, *psrc_end = psrc + LED_CNT_SPI;
    uint32_t w = 0;
    // Iterate brightness
    for(uint32_t brt=1; brt<=TIMESLOT_TOP; brt++) {
        while(psrc < psrc_end) { // Iterate LEDs. Pic size is equal to LED cnt.
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 0);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 1);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 2);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 3);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 4);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 5);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 6);
            if(*psrc++ >= brt) w |= (uint32_t)(1UL << 7);
            // Put it to Raw Buf
            *pdst-- = (uint8_t)w;
            w = 0;
        } // for N
        // Pic bytes iterated, prepare for next brt
        pdst += (1 + LED_BYTE_CNT + (LED_BYTE_CNT - 1)); // Eliminate last --, move to next part, move to last element of next part
        psrc = pic;
    } // brt
    // Set central LED
    central_LED.Set(pic[(LED_CNT_TOTAL - 1UL)]); // Last byte for central LED
    new_pic_is_ready = true; // Say it is ready
//    DBG_LO();
}
void SetCentralLed(uint8_t brt) {
    central_LED.Set(brt);
}

} // Namespace
